<?php

/**
 * \file
 * Classe `Object`.
 *
 * \since 07/10/2008
 * \author Matthieu OVIEDO <matthieu.oviedo@gmail.com>
 */

/**
 * Objets d'entités.
 */ 
namespace pilar\Objects /* \cond */ ; /* \endcond */
{

/**
 * Objet d'une entité.
 *
 * \since 07/10/2008
 * \author Matthieu OVIEDO <matthieu.oviedo@gmail.com>
 */
class Object {

/**
 * Identifiant numérique.
 *
 * \since 07/10/2008
 */
private $_id;


/**
 * Entité type de l'objet.
 *
 * \since 07/10/2008
 */
private $_entity;


/**
 * Membres instanciés de l'objet.
 *
 * \since 07/10/2008
 */
private $_members;


/**
 * Liens liées à l'objet.
 *
 * \since 27/02/2009
 */
private $_links = array();


/**
 * Score de l'objet dans une recherche.
 *
 * \since 05/03/2009
 */
public $rating;

/**
 * Constructeur.
 *
 * \since 07/10/2008
 * \param $entity Entité type de l'objet.
 */
public function __construct($entity) {
 $this->_id = null;
 $this->_entity = $entity;
 $this->_members = array();
 $this->rating = 0;
}


/**
 * Accède à l'élément associé à un membre donné.
 *
 * \param $name Nom du membre.
 * \return Élément.
 *
 * \since 07/10/2008
 */
public function __get($name) {
 return $this->member($name)->__getLink();
}


/**
 * Accède à un membre de l'objet par son nom et le retourne.
 * 
 * Un chargement dynamique avec cache est géré.
 *
 * \param $name Nom du membre.
 * \return Le membre requis.
 *
 * \since 07/03/2009
 */
public function member($name) {
 // Si le membre a déjà été chargé on retourne directement son instance :
  if(isset($this->_members[$name])) {
   return $this->_members[$name];
  }
  
 // A ce stade il faut instancier un nouveau membre.
 
 // Evaluation du membre de l'entité :
  $entityMember = $this->_entity->member($name);
  
 // Création du nouveau membre à partir de la méthode dédiée du membre de
 // l'entité :
  $member = $entityMember->newObjectMember();
  
 // On s'associe au membre en tant que son objet parent :
  $member->setParentObject($this);
 
 // Ajout du membre à la liste locale :
  $this->_members[$name] = $member;
  
 return $member;
}


/**
 * Assigne une valeur au champ principal d'un membre.
 *
 * \param $name Nom du membre.
 * \param $value Valeur à assigner.
 *
 * \since 26/02/2009
 */
public function __set($name, $value) {
 $this->member($name)->set($value);
}


/**
 * Insère l'objet en base de données.
 *
 * \since 07/10/2008
 * \return $this.
 */
private function insert() {
 // On s'assure que l'objet n'est pas déjà existant en base de données :
  if($this->_id != null) {
   trigger_error(
    "Cet objet existe déjà en base de données. Impossible de l'insérer."
   );
   return $this;
  }
  
 // A ce stade l'objet n'existe pas encore en base de données.
 
 // Génération de la requête INSERT :
  $fields = array();
  $values = array();
  foreach($this->_entity->members as $entityMember) {
   $member = $this->member($entityMember->get('name'));
   foreach($entityMember->fields as $efn => $ef) {
    $field = $member->field($efn);
    $fields[] = '`'.$ef->fullName().'`';
    $values[] = "'".\db\escape($field->get())."'";
   }
  }
  $sqlCmd = 'INSERT INTO `'.$this->_entity->sqlTableName().'` '
          . '('.implode(', ', $fields).') VALUES ('.implode(', ', $values).')';
  
 // Exécution de la requête et récupération de l'ID :
  \db\execute($sqlCmd);
  $id = \db\insertId();

 $this->_entity->touch();
  
 // Enfin on flush l'objet :
  $this->flush();
  
 // On s'attribue ensuite l'ID :
  $this->setId($id);
  
 return $this;
}


/**
 * Change les valeurs des champs de l'objet.
 *
 * \param $values Arbre des valeurs.
 * \return $this
 *
 * \since 07/10/2008
 */
public function set($values) {
 foreach($values as $memberName => $value) {
  $this->member($memberName)->set($value);
 }
 
 return $this;
}


/**
 * Enregistre les modifications de l'objet en base de données.
 *
 * \since 07/10/2008
 * \return $this.
 */
private function update() {
 // On s'assure que l'objet est bien existant en base de données :
  if($this->_id == null) {
   trigger_error(
    "Cet objet n'existe pas en base de données. Impossible de le mettre à jour."
   );
   return $this;
  }
  
 // A ce stade l'objet existe bien en base de données.
 
 // Récupération des codes d'assignations :
  $fields = array(); // Codes des assignations.
  foreach($this->_members as $member) {
   foreach($member->fields() as $field) {
    if($field->changed()) $fields[] = $field->assignSqlCode();
   }
  }
  
 // Si aucune assignation alors on sort :
  if(empty($fields)) return $this;
  
 // A ce stade il y a des mises à jours à faire.
 
 // Génération de la requête UPDATE :
  $sqlCmd = 'UPDATE `'.$this->_entity->sqlTableName().'` '
          . 'SET '.implode(', ', $fields).' '
          . 'WHERE `idValue`=\''.$this->_id.'\' LIMIT 1';
  
 // Exécution de la requête d'édition:
  \db\execute($sqlCmd);

 $this->_entity->touch();
  
 // Enfin on flush l'objet :
  $this->flush();
  
 return $this;
}


/**
 * Insère l'objet si celui-ci n'existe pas encore en base de données
 * et le met à jour sinon.
 *
 * \return $this
 *
 * \see update
 * \see insert
 *
 * \since 28/10/2008
 */
public function save() {
 // Déclenchement de l'évènement de sauvegarde :
  foreach($this->_members as $member) $member->onSave();

 if($this->_id == null) return $this->insert();
 else return $this->update();
}


/**
 * Supprime l'objet de la base de données.
 *
 * \since 07/10/2008
 * \return $this.
 */
public function delete() {
 // On s'assure que l'objet est bien existant en base de données :
  if($this->_id == null) {
   trigger_error(
    "Cet objet n'existe pas en base de données. Impossible de le supprimer."
   );
   return $this;
  }
  
 // A ce stade l'objet existe bien en base de données.
 
 // Génération de la requête DELETE :
  $sqlCmd = 'DELETE FROM `'.$this->_entity->sqlTableName().'` '
          . 'WHERE `idValue`=\''.$this->_id.'\' LIMIT 1';
  
 // Exécution de la requête de suppression:
  \db\execute($sqlCmd);

 $this->_entity->touch();
  
 // Remise à zéro de l'objet :
  $this->reset();
  
 return $this;
}


/**
 * Description DOM de l'objet.
 *
 * \param $domDoc \DOMDocument a utiliser comme document parent.
 * \return Noeud DOM de la description de l'objet.
 *
 * \since 07/10/2008
 */
public function domNode_meta($domDoc) {
 $domObject = $domDoc->createElement('object');

 foreach($this->_members as $member) {
  $domObject->appendChild($member->domNode_meta($domDoc));
 }

 return $domObject;
}


/**
 * Version DOM de l'objet.
 *
 * \param $domDoc \DOMDocument a utiliser comme document parent.
 * \param $name Nom à donner à l'élément racine (par défaut le nom de
 *              l'entité associée à l'objet).
 * \return Noeud DOM de l'objet.
 *
 * \since 10/12/2008
 */
public function domNode($domDoc, $name = null) {
 if ($name == null) $name = $this->_entity->get('name');

 $domObject = $domDoc->createElement($name);

 foreach($this->_members as $member) {
  $domObject->appendChild($member->domNode($domDoc));
 }

 foreach($this->_links as $link) {
  $domObject->appendChild($link->domNode($domDoc));
 }
 
 return $domObject;
}


/**
 * Flush l'objet.
 *
 * \since 07/10/2008
 */
private function flush() {
 foreach($this->_members as $member) {
  $member->flush();
 }
}


/**
 * Change l'ID de l'objet.
 *
 * \since 07/10/2008
 * \param $id Nouvel ID.
 */
public function setId($id) {
 // On s'assure que l'identifiant soit nul :
  if($this->_id != null) {
   trigger_error(
    "Il est interdit de changer la valeur de l'identifiant d'un objet."
   );
   return;
  }
  
 // Changement de l'identifiant :
  $this->_id = $id;
  
 // Mise à jour de la valeur du champ `id` :
  $this->member('id')->sync($id);
}


/**
 * ID de l'objet.
 *
 * \since 28/10/2008
 * \return ID de l'objet.
 */
public function id() {
 return $this->_id;
}


/**
 * Remet à zéro l'objet.
 *
 * \since 07/10/2008
 */
public function reset() {
 $this->_id = null;
 
 foreach($this->_members as $member) {
  $member->reset();
 }
}


/**
 * Entité type de l'objet.
 *
 * \since 28/10/2008
 * \return Entité type de l'objet.
 */
public function entity() {
 return $this->_entity;
}


/**
 * Ajouter un lien à l'objet.
 *
 * \since 27/02/2009
 */
public function addLink($link) {
 $this->_links[] = $link;
}


/**
 * Retourne les liens.
 *
 * \since 27/02/2009
 */
public function links() {
 return $this->_links;
}


} // Class.

} // Namespace.

?>
